package com.yunwushanhe.document.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

@Slf4j
@Data
@Component
@NoArgsConstructor
@AllArgsConstructor
public class FileUtils {
    //工具类里面最好不要定义变量，如果非要定义，也应该是那种只被读而不是拿来修改的变量。
    //如果实在要定义一些拿来修改的变量，那么在对应使用这个变量的方法中，最首先的代码是对这个变量进行类似清空的处理，可以减少在单例模式中并发执行几段程序出错的肯能。
    private String message = ""; //用于表示重复上传文件或者文件夹的信息，并返回给前端
    private String messageRD = ""; //用于表示返回上级目录的信息，并返回给前端
    private String userNumberRecord = "";//记录从userController中获取到的userNumber;
    private ArrayList<Node> fileList = new ArrayList<>();
    private ArrayList<Node> folderList = new ArrayList<>();
    private HashMap<String, String> folderMapOne = new HashMap<>();
    private HashMap<String, HashMap<String, String>> folderMapTwo = new HashMap<>();

    /**
     * 获取系统文件路径最开始的绝对路径
     *
     * @return
     */
    public String getSystemPath() {
        ApplicationHome applicationHome = new ApplicationHome(this.getClass());
        String path = applicationHome.getDir().getAbsolutePath() + "\\static\\files\\"+userNumberRecord;// \target\classes
        return path;
    }

    /**
     * 创建文件夹(单)
     *
     * @param filename 文件夹的名称
     */
    public boolean createFolder(String filename, String filePath) {
        File file = new File(this.getSystemPath() + this.remakePath(filePath) + filename);
        boolean flag = file.mkdir();
        return flag;
        //java.io.IOException: 系统找不到指定的路径。
    }

    /**
     * 创建文件夹(多)
     */
    public boolean createFolders(String filePath) {
        File file = new File(this.getSystemPath() + this.remakePath(filePath));
        boolean flag = file.mkdirs();
        return flag;
        //java.io.IOException: 系统找不到指定的路径。
    }

    /**
     * 保存上传的文件
     *
     * @param filePath 传入需要保存的路径
     * @param files    上传的文件
     *                 (一个问题重复上传同一目录)
     */
    public boolean saveUploadFile(String filePath, MultipartFile[] files) throws IOException {
        this.fileList.clear();
        boolean flag = true;
        if (!files[0].isEmpty()) {
            for (MultipartFile file : files) {
                String filename = file.getOriginalFilename();
                long fileSize = file.getSize();
                Node node = new Node();
                node.setName(filename);
                node.setPath(filePath);
                node.setSize(this.remakeSize(fileSize));
                if (this.surePath(this.getSystemPath() + this.remakePath(filePath) + filename)) {
                    this.message = this.message + filename + " ";
                    continue;
                }
                this.fileList.add(node);
                log.info("查看路径xxxxxxxxxxxxxxxxxx={}", filePath);
                log.info("文件：filename = {},fileSize = {}B", filename, fileSize);
                file.transferTo(new File(this.getSystemPath() + this.remakePath(filePath) + filename));
            }//两次出错应该都是找不到路径，D:\JavaWork\JavaProject\springboot_document\target\classes\static\files\Python第二次作业.docx (系统找不到指定的路径。)
            if (!this.message.isEmpty()) {
                this.message = this.message + "这些文件与系统文件重名，请勿重复上传！";
            }
        } else {
            flag = false;
            this.message = "请选中文件之后再上传!";
        }
        return flag;
    }

    /**
     * 保存上传的文件夹
     *
     * @param filePath 传入需要保存的路径
     * @param folders  上传的文件夹
     *                 (一个问题重复上传同一目录)
     *///这里有一个问题，在判断重复上传的文件时有些问题，需要修改一下
    public boolean saveUploadFolder(String filePath, MultipartFile[] folders) throws IOException {
        this.folderList.clear();
        boolean flag = true;
        boolean createFolders = false;
        HashSet<String> set = new HashSet<>();

        if (!folders[0].isEmpty()) {//是否选中文件
            for (MultipartFile folder : folders) {
                String folderName = folder.getOriginalFilename();
                int index = folderName.lastIndexOf("/");
                log.info("文件夹上传判断索引 = {}", index);
                String path = folderName.substring(0, index);
                set.add(path);//set集合里面存储的是文件夹中所有文件相对于该文件夹的路径（以/为分割的,末尾并不包含/）
                long folderSize = folder.getSize();
                Node node = new Node();
                node.setName(folderName.substring(index + 1));
                node.setPath(filePath + path + "/");
                node.setSize(this.remakeSize(folderSize));
                this.folderList.add(node);//存储该文件夹中的所有文件以及他的全路径
                log.info("文件夹：folderName = {},index = {},path = {},folderSize = {}B", folderName, index, path, folderSize);
            }
            log.info("纠错02<--=============={}==============-->",folderList.get(0).getPath());
            log.info("纠错03<--=============={}==============-->",folderList.get(0).getPath().length());
            log.info("纠错04<--=============={}==============-->",filePath.length());
            String topFolderPath = folderList.get(0).getPath().substring(filePath.length());
            log.info("纠错01<--=============={}==============-->",topFolderPath);
            int index = topFolderPath.indexOf("/");
            String judgePath = filePath+topFolderPath.substring(0,index);//filePath加上顶层文件名就可以了
            log.info("判断路劲<--=============={}==============-->",judgePath);
            createFolders = !this.surePath(this.getSystemPath()+this.remakePath(judgePath));
            //这里判断是否重复上传文件夹的方法只用判断最顶层的就可以了

            /*this.getFoldersTwo(filePath, set);//调用获取文件夹路径的函数（此路径代表上传文件夹的中所有子文件夹对应的路径）
            for (String path : set) {//这里先把文件夹创建出来
                this.createFolders(this.remakePath(filePath) + path);//调用创建多级文件夹的函数
                log.info("判断={}", createFolders);
            }*/
            if (createFolders) {//如果文件夹创建成功才执行上传文件的操作，是为了防止重复上传文件夹
                this.getFoldersTwo(filePath, set);//调用获取文件夹路径的函数（此路径代表上传文件夹的中所有子文件夹对应的路径）

                for (String path : set) {//这里先把文件夹创建出来
                    this.createFolders(this.remakePath(filePath) + path);//调用创建多级文件夹的函数
                    log.info("判断={}", createFolders);
                }

                for (MultipartFile folder : folders) {//这个for循环正式开始上传文件夹中的所有文件了
                    String folderName = folder.getOriginalFilename();//这里不仅是文件名，还带有文件夹路径（以\分割）
                    folder.transferTo(new File(this.getSystemPath() + this.remakePath(filePath) + folderName));//这里依然是以单个文件的形式上传文件夹中的内容的
                }
            } else {
                this.message = "上传文件夹与系统中文件夹重名，请勿重复上传！";
            }
        } else {
            flag = false;
            this.message = "请选中文件夹之后再上传!";
        }
        return flag && createFolders;
    }

    /**
     * 方法一，获取每个文件夹的路径（文件夹的名称不能重名）
     *
     * @param filePath 上一级级之上的路径
     * @param set      筛选出来的所有到底不重复的路径
     * @return
     */
    public HashMap<String, String> getFoldersOne(String filePath, HashSet<String> set) {
        this.folderMapOne.clear();
        for (String folderPath : set) {
            String[] split = folderPath.split("/");
            String[] record = new String[split.length];
            record[0] = filePath;
            for (int i = split.length - 1; i > 0; i--) {
                record[i] = filePath + folderPath.substring(0, folderPath.lastIndexOf("/") + 1);
                folderPath = folderPath.substring(0, folderPath.lastIndexOf("/"));
            }
            for (int i = 0; i < split.length; i++) {
                this.folderMapOne.put(split[i], record[i]);
            }
        }
        return this.folderMapOne;
    }

    /**
     * 方法二，获取每个文件夹的路径（文件夹的名称可以正常重名）
     *
     * @param filePath 上一级级之上的路径
     * @param set      筛选出来的所有到底不重复的路径
     * @return
     */
    public HashMap<String, HashMap<String, String>> getFoldersTwo(String filePath, HashSet<String> set) {
        this.folderMapTwo.clear();
        for (String folderPath : set) {
            String[] split = folderPath.split("/");
            String[] record = new String[split.length];
            record[0] = filePath;
            for (int i = split.length - 1; i > 0; i--) {
                record[i] = filePath + folderPath.substring(0, folderPath.lastIndexOf("/") + 1);
                folderPath = folderPath.substring(0, folderPath.lastIndexOf("/"));
            }
            for (int i = 0; i < split.length; i++) {
                HashMap<String, String> map = new HashMap<>();
                map.put(split[i], record[i]);
                this.folderMapTwo.put(record[i] + split[i], map);
            }
        }
        return this.folderMapTwo;
    }

    /**
     * 确认文件路径是否存在
     *
     * @param absolutePath 传入需要判断的路径
     */
    public boolean surePath(String absolutePath) {
        File file = new File(absolutePath);
        return file.exists();
    }

    /**
     * 把数据库中的文件路径格式改变一下
     *
     * @param path
     */
    public String remakePath(String path) {
        String remakePath = path.replace("/", "\\");
        return remakePath;
    }

    /**
     * 变换文件大小单位
     *
     * @param fileSize 文件多少字节
     * @return
     */
    public String remakeSize(long fileSize) {
        if (fileSize < 1024) {
            return fileSize + "B";
        } else if (1024 <= fileSize && fileSize < 1024 * 1024) {
            return fileSize / 1024 + "KB";
        } else if (1024 * 1024 <= fileSize && fileSize < 1024 * 1024 * 1024) {
            return fileSize / (1024 * 1024) + "MB";
        } else {
            return fileSize / (1024 * 1024 * 1024) + "GB";
        }
    }

    /**
     * 重命名
     *
     * @param OriginalFilePath 传入需要重名文件的路劲（带文件名），不包括系统路劲那一部分，是相对路径
     * @param newFilePath      传入已经修改好的文件路劲(带文件名)，不包括系统路劲那一部分，是相对路径
     * @return 返回重名结果（防止出现重名了）false则代表重名及失败了
     */
    public boolean rename(String OriginalFilePath, String newFilePath) {
        String systemPath = this.getSystemPath();
        String OriginalFileAbsolutePath = systemPath + this.remakePath(OriginalFilePath);
        String newFileAbsolutePath = systemPath + this.remakePath(newFilePath);
        File originalFile = new File(OriginalFileAbsolutePath);
        File newFile = new File(newFileAbsolutePath);
        return originalFile.renameTo(newFile);
    }

    /**
     * 删除文件或者删除某个文件夹下的所有文件夹和文件
     * @param filename 删除文件的名称
     * @param filePath 删除文件在数据库中存储的路径
     * @return
     */
    public boolean deleteFile(String filename, String filePath) {
        boolean flag;
        String defPath = this.getSystemPath() + this.remakePath(filePath + filename);
        File file = new File(defPath);
        flag = file.exists();//文件或者文件夹不存在，必定会出错，存在一般不会
        if (!file.isDirectory()) {//判断是否是文件夹，不是这进行这个if里面的操作
            log.info("xxxxxxxxxxxxxxxx = {}", 111111);
            flag = file.delete();
        } else {
            try {
                log.info("xxxxxxxxxxxxxxxx = {}", 222222);
                Path path = Paths.get(defPath);
                Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        Files.delete(file);
                        System.out.printf("文件被删除 : %s%n", file);
                        return FileVisitResult.CONTINUE;
                    }

                    @Override
                    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                        Files.delete(dir);
                        System.out.printf("文件夹被删除: %s%n", dir);
                        return FileVisitResult.CONTINUE;
                    }
                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 文件下载功能
     * @param response
     * @param filePath
     * @param filename
     * @return
     *///下载功能再中文命名这里有些问题
    public boolean downloadFile(HttpServletResponse response, String filePath, String filename) {
        String absolutePath = this.getSystemPath() + this.remakePath(filePath) + filename;
        File file = new File(absolutePath);
        boolean flag = file.exists();
        if (flag) {
            try {
                // 设置响应头，URLEncoder.encode(filename,"utf-8")  文件名包含中文时需要进行中文编码，否则会出现乱码问题
                response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename,"utf-8"));
                /*上面这行代码是在为启动该浏览器的下载功能而用的*/
                InputStream in = new FileInputStream(file);
                ServletOutputStream out = response.getOutputStream();
                byte[] bytes = new byte[1024];
                int len = 0;
                while ((len = in.read(bytes)) != -1) {
                    out.write(bytes, 0, len);
                }
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 复制文件的功能
     * @param copyFile
     * @param targetFile
     * @return
     */
    public boolean copyFile(com.yunwushanhe.document.entity.File copyFile,
                            com.yunwushanhe.document.entity.File targetFile) {
        String targetFilePath = this.remakePath(targetFile.getFilePath()) + targetFile.getFilename() + "\\";
        boolean judgeDuplicate = this.surePath(this.getSystemPath() + targetFilePath + copyFile.getFilename());
        if (judgeDuplicate) {//解决复制文件夹的时候目标文件夹下面有相同文件的问题
            return false;
        }
        String copyFileAbsolutePath = this.getSystemPath() + this.remakePath(copyFile.getFilePath()) + copyFile.getFilename();
        String targetFileAbsolutePath = this.getSystemPath() + this.remakePath(targetFile.getFilePath()) + targetFile.getFilename() + "\\" + copyFile.getFilename();
        try {
            FileInputStream fis = new FileInputStream(copyFileAbsolutePath);
            FileOutputStream fos = new FileOutputStream(targetFileAbsolutePath);
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, length);
            }
            fos.close();
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 复制文件的功能2
     * 这个共能没有被使用
     * @param copyFile 待复制的文件
     * @param targetFilePath = targetFile.getFilePath() + targetFile.getFilename() + "\\";目标路径
     * @return
     */
    public boolean copyFileTwo(com.yunwushanhe.document.entity.File copyFile, String targetFilePath) {
        boolean judgeDuplicate = this.surePath(this.getSystemPath() + targetFilePath + copyFile.getFilename());
        if (judgeDuplicate) {//解决复制文件夹的时候目标文件夹下面有相同文件的问题
            return false;
        }
        String copyFileAbsolutePath = this.getSystemPath() + this.remakePath(copyFile.getFilePath()) + copyFile.getFilename();
        String targetFileAbsolutePath = this.getSystemPath() + this.remakePath(targetFilePath)+ copyFile.getFilename();
        try {
            FileInputStream fis = new FileInputStream(copyFileAbsolutePath);
            FileOutputStream fos = new FileOutputStream(targetFileAbsolutePath);
            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = fis.read(bytes)) != -1) {
                fos.write(bytes, 0, length);
            }
            fos.close();
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 复制文件夹的功能
     *
     * @param copyFile
     * @param targetFile
     * @param fileList   代表复制文件夹中包含的文件以及文件夹
     * @return
     */
    public boolean copyFolder(com.yunwushanhe.document.entity.File copyFile,
                              com.yunwushanhe.document.entity.File targetFile,
                              List<com.yunwushanhe.document.entity.File> fileList) {
        HashSet<String> set = new HashSet<>();
        boolean flag = false;
        int copyFilePathLength = copyFile.getFilePath().length();
        String targetFilePath = this.remakePath(targetFile.getFilePath()) + targetFile.getFilename() + "\\";
        boolean judgeDuplicate = this.surePath(this.getSystemPath() + targetFilePath + copyFile.getFilename());
        if (judgeDuplicate) {//解决复制文件夹的时候目标文件夹下面有相同文件的问题
            return false;
        }
        if (fileList.isEmpty()) {//复制空文件夹
            flag = this.createFolder(copyFile.getFilename(), targetFilePath);
        } else {
            for (com.yunwushanhe.document.entity.File file : fileList) {//这些都是子文件，在截取路劲的操作中，截取到了复制文件的名称的
                String interceptedPath = file.getFilePath();
                String finishedPath = targetFilePath + this.remakePath(interceptedPath.substring(copyFilePathLength));
                set.add(file.getFileJudge() == 1 ? (finishedPath + file.getFilename() + "/") : finishedPath);
            }
            Iterator<String> it = set.iterator();
            while (it.hasNext()) {//这一步是先把文件夹创建出来
                String relativePath = it.next();
                this.createFolders(relativePath);//把复制文件夹下面所有的文件夹都先在目标文件夹下面创建出来(执行的时候是无序的)
            }
            for (com.yunwushanhe.document.entity.File file : fileList) {//这里需要进行筛选一下，把文件夹选出去
                if(file.getFileJudge()!=1){
                    String interceptedPath = file.getFilePath();
                    String newTargetFilePath = targetFilePath + this.remakePath(interceptedPath.substring(copyFilePathLength));// 末尾带/
                    this.copyFileTwo(file,newTargetFilePath);
                }
            }
            flag = true;
        }
        return flag;
    }//只需判断最顶层的文件

    /**
     * 复制文件夹或者文件的功能
     * @param copyFile
     * @param targetFile
     * @param fileList   代表复制文件夹时中包含的文件以及文件夹
     * @return
     */
    public boolean copyFileOrFolder(com.yunwushanhe.document.entity.File copyFile,
                                    com.yunwushanhe.document.entity.File targetFile,
                                    List<com.yunwushanhe.document.entity.File> fileList) {
        if (copyFile.getFileJudge() == 1) {//移动文件夹
            return this.copyFolder(copyFile, targetFile, fileList);
        } else {
            return this.copyFile(copyFile, targetFile);
        }
    }

    /**
     * 移动文件的功能（先进行复制然后再删除，如果复制失败，就没必要删除。
     * 并且删除一定成功，复制不一定，有这么一个先后顺序）
     * @param removeFile
     * @param targetFile
     * @return
     */
    public boolean removeFile(com.yunwushanhe.document.entity.File removeFile,
                              com.yunwushanhe.document.entity.File targetFile) {
        return this.copyFile(removeFile,targetFile)&&this.deleteFile(removeFile.getFilename(),removeFile.getFilePath());
    }

    /**
     * 移动文件夹的功能（先进行复制然后再删除，如果复制失败，就没必要删除。
     * 并且删除一定成功，复制不一定，有这么一个先后顺序）
     * @param removeFile
     * @param targetFile
     * @param fileList  代表移动文件夹中包含的文件以及文件夹
     * @return
     */
    public boolean removeFolder(com.yunwushanhe.document.entity.File removeFile,
                                com.yunwushanhe.document.entity.File targetFile,
                                List<com.yunwushanhe.document.entity.File> fileList) {
        return this.copyFolder(removeFile,targetFile,fileList)&&this.deleteFile(removeFile.getFilename(),removeFile.getFilePath());
    }

    /**
     * 移动文件或者文件夹的功能
     * @param removeFile
     * @param targetFile
     * @param fileList  代表移动文件夹中包含的文件以及文件夹
     * @return
     */
    public boolean removeFileOrFolder(com.yunwushanhe.document.entity.File removeFile,
                                com.yunwushanhe.document.entity.File targetFile,
                                List<com.yunwushanhe.document.entity.File> fileList) {
        if (removeFile.getFileJudge()==1) {//移动文件夹
            return this.removeFolder(removeFile,targetFile,fileList);
        } else {
            return this.removeFile(removeFile,targetFile);
        }
    }

    /**
     * 用于在登录的时候记录本次登录的账号
     * @param userNumber 用户账号
     */
    /*public void recordUserNumber(String userNumber){
        try {
            String systemPath = this.getSystemPath();
            int index = userNumber.indexOf("@");
            String recordUserNumber = userNumber.substring(0, index);
            FileOutputStream fos = new FileOutputStream(systemPath + "\\recordUserNumber\\" + recordUserNumber + ".txt");
            fos.write(userNumber.getBytes());
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }*/

    /**
     * 用于删除登陆时所创建的记录文件
     * @param userNumber 用户账号
     */
    /*public void deleteRecordUserNumber(String userNumber){
        int index = userNumber.indexOf("@");
        String recordUserNumber = userNumber.substring(0, index);
        String delPath = this.getSystemPath()+"\\recordUserNumber\\" + recordUserNumber + ".txt";
        File file = new File(delPath);
        boolean delete = file.delete();
    }*/

    /**
     * 创建用户顶层文件夹的功能
     * @param userNumber
     */
    public void createUserTopFolder(String userNumber){
        ApplicationHome applicationHome = new ApplicationHome(this.getClass());
        String path = applicationHome.getDir().getAbsolutePath() + "\\static\\files";
        int index = userNumber.indexOf("@");
        String recordUserNumber = userNumber.substring(0, index);
        File file = new File(path + "\\" + recordUserNumber);
        boolean mkdir = file.mkdir();
    }

    /**
     * 用于存储基本的信息量
     */
    public class Node {
        private String name;
        private String path;
        private String size;

        public Node() {
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getSize() {
            return size;
        }

        public void setSize(String size) {
            this.size = size;
        }
    }
}
